﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Tyshka.Subroutines.Domain.Abstract;
using Tyshka.Subroutines.Domain.Entities;
using Tyshka.Subroutines.Domain.Infrastructure.Auth;
using Tyshka.Subroutines.Filters;
using Tyshka.Subroutines.ViewModels;

namespace Tyshka.Subroutines.Controllers
{
    [AuthenticationFilter(false)]
    public class SubroutineController : Controller
    {
        //
        // GET: /Subroutine/

        public ActionResult Index()
        {
            var repository = (ISubroutineRepository)DependencyResolver.Current.GetService(typeof(ISubroutineRepository));
            var query = repository.Query();

            var userRepository = (IUserRepository) DependencyResolver.Current.GetService(typeof (IUserRepository));
            var userQuery = userRepository.Query();

            var model = new SubroutineListingModel
                            {
                                Items = new List<SubroutineListingItemWrapper>(), 
                                SubroutineCount = query.Count(),
                                UserCount = userQuery.Count(),
                                UploadAllowed = LoginUtils.CurrentUser != null && LoginUtils.CurrentUser.IsLogged,
                            };

            foreach (var subroutine in query)
            {
                model.Items.Add(
                    new SubroutineListingItemWrapper
                        {
                            Id = subroutine.Id,
                            Name = subroutine.Title,
                            Author = subroutine.User.FullName,
                            Votes = subroutine.Votes,
                       }
                   ); 
            }
            
            return View("Index", model);
        }

        public ActionResult Raw(long id)
        {
            var repository = (ISubroutineRepository)DependencyResolver.Current.GetService(typeof(ISubroutineRepository));

            var targetSub = repository.Read(id);

            if (targetSub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            var model = new RawDataModel
                            {
                                Code = targetSub.Code.Replace("\n", "<br />"),
                            };

            return View("Raw", model);
        }

        public ActionResult Detail(long id)
        {
            var repository = (ISubroutineRepository)DependencyResolver.Current.GetService(typeof(ISubroutineRepository));
            
            var targetSub = repository.Read(id);

            if (targetSub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            var model = new DetailViewModel
                            {
                                Title = targetSub.Title,
                                Author = targetSub.User.FullName,
                                DateAdded = targetSub.Date.ToString("MMMM dd, yyyy"),
                                IsCanVote =
                                    LoginUtils.CurrentUser != null && LoginUtils.CurrentUser.IsLogged &&
                                    !targetSub.UserVotes.Any(uv => uv.UserId == LoginUtils.CurrentUser.Id),
                                Votes = targetSub.Votes,
                                Description = targetSub.Description,
                                Code = targetSub.Code,
                                BadgeCssStyle = targetSub.Votes >= 0 ? "badge-success" : "badge-important",
                                Tags = targetSub.SubroutineTags.ToDictionary(st => st.TagId, st => st.Tag.Name),
                                IsAnyRegisterParams = targetSub.SubroutineInputRegisters != null && targetSub.SubroutineInputRegisters.Count > 0,
                                RegisterParams = targetSub.SubroutineInputRegisters == null ? new List<RegisterParamWrapper>() : targetSub.SubroutineInputRegisters.Select(sir => new RegisterParamWrapper {Comment = sir.Description, Name = sir.Register.Name}).ToList(),
                                IsAnyStackParams = targetSub.InputStackParams != null && targetSub.InputStackParams.Count > 0,
                                StackParams = targetSub.InputStackParams == null ? new List<StackParamWrapper>() : targetSub.InputStackParams.Select(isp => new StackParamWrapper {Comment = isp.Description, Offset = isp.Offset}).ToList(),
                                IsAnyOutput = targetSub.SubroutineOutputRegisters != null && targetSub.SubroutineOutputRegisters.Count > 0,
                                OutputParams = targetSub.SubroutineOutputRegisters == null ? new List<RegisterParamWrapper>() : targetSub.SubroutineOutputRegisters.Select(sir => new RegisterParamWrapper { Comment = sir.Description, Name = sir.Register.Name }).ToList(),
                                Alts = targetSub.CompiledAlternatives.Select(c => new AlternativeWrapper { Author = c.User.FullName, Id = c.Id, Name = c.Title, Votes = c.Votes, VotesCssClass = c.Votes >= 0 ? "badge-success" : "badge-important" }).ToList(),
                                Deps = targetSub.CompiledDependencies.Select(c => new AlternativeWrapper { Author = c.User.FullName, Id = c.Id, Name = c.Title, Votes = c.Votes, VotesCssClass = c.Votes >= 0 ? "badge-success" : "badge-important" }).ToList(),
                                UploadAllowed = LoginUtils.CurrentUser != null && LoginUtils.CurrentUser.Id == targetSub.UserId,
                                Id = targetSub.Id,
                            };
            model.HasAlts = model.Alts != null && model.Alts.Count > 0;
            model.HasDeps = model.Deps != null && model.Deps.Count > 0;
            return View("Detail", model);

        }


        public ActionResult New()
        {

            var registerRepo = (IRegisterRepository) DependencyResolver.Current.GetService<IRegisterRepository>();
            var allRegisters = registerRepo.Query();

            var model = new SubEditModel();
            model.Id = 0;
            model.InputRegisters = new List<InputRegisterDataWrapper>();
            model.OutputRegisters = new List<InputRegisterDataWrapper>();
            model.TagLine = String.Empty;

            model.Stacks = new List<StackWrapper>();

            for (int i = 0; i < 5; i++ )
            {
                model.Stacks.Add(new StackWrapper {Offset = i, Comment = String.Empty});
            }

            foreach (var register in allRegisters)
            {
                model.InputRegisters.Add(new InputRegisterDataWrapper { Comment = String.Empty, IsUsed = false, Name = register.Name, RegisterId = register.Id });
                model.OutputRegisters.Add(new InputRegisterDataWrapper { Comment = String.Empty, IsUsed = false, Name = register.Name, RegisterId = register.Id });
            }

            return View("Edit", model);
        }

        public ActionResult Delete(long id)
        {
            var subRepo = DependencyResolver.Current.GetService<ISubroutineRepository>();
            var sub = subRepo.Read(id);

            if (sub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            if (LoginUtils.CurrentUser == null || sub.UserId != LoginUtils.CurrentUser.Id)
            {
                return RedirectToAction("Error", "Error");
            }
            
            var subTagRepo = DependencyResolver.Current.GetService<ISubroutineTagRepository>();
            var subTags = subTagRepo.Query(st => st.SubroutineId == id).ToList();
            foreach (var subroutineTag in subTags)
            {
                subTagRepo.Delete(subroutineTag);
            }

            var userVoteRepo = DependencyResolver.Current.GetService<IUserVoteRepository>();
            var userVotes = userVoteRepo.Query(uv => uv.SubroutineId == id).ToList();
            foreach (var userVote in userVotes)
            {
                userVoteRepo.Delete(userVote);
            }

            var inputRepo = DependencyResolver.Current.GetService<ISubroutineInputRegisterRepository>();
            var inputRegs = inputRepo.Query(ir => ir.SubroutineId == id).ToList();
            foreach (var subroutineInputRegister in inputRegs)
            {
                inputRepo.Delete(subroutineInputRegister);
            }

            var outputRepo = DependencyResolver.Current.GetService<ISubroutineOutputRegisterRepository>();
            var outputRegs = outputRepo.Query(or => or.SubroutineId == id).ToList();
            foreach (var subroutineOutputRegister in outputRegs)
            {
                outputRepo.Delete(subroutineOutputRegister);
            }

            var stackRepo = DependencyResolver.Current.GetService<IInputStackParamRepository>();
            var stacks = stackRepo.Query(s => s.SubroutineId == id).ToList();
            foreach (var inputStackParam in stacks)
            {
                stackRepo.Delete(inputStackParam);
            }

            var alterRepo = DependencyResolver.Current.GetService<IAlternativeRepository>();
            var alters = alterRepo.Query(a => a.FirstSubroutineId == id || a.SecondSubroutineId == id).ToList();
            foreach (var alternative in alters)
            {
                alterRepo.Delete(alternative);
            }

            var depRepo = DependencyResolver.Current.GetService<IDependencyRepository>();
            var deps = depRepo.Query(d => d.UsedId == id || d.CallerId == id).ToList();
            foreach (var dependency in deps)
            {
                depRepo.Delete(dependency);
            }

            sub = subRepo.Read(id);
            subRepo.Delete(sub);

            return RedirectToAction("Index", "Subroutine");
        }

        public ActionResult Edit(long id)
        {
            var registerRepo = (IRegisterRepository)DependencyResolver.Current.GetService<IRegisterRepository>();
            var allRegisters = registerRepo.Query();

            var model = new SubEditModel();

            var subRepo = (ISubroutineRepository) DependencyResolver.Current.GetService<ISubroutineRepository>();
            var sub = subRepo.Read(id);

            if (sub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            if (LoginUtils.CurrentUser == null || sub.UserId != LoginUtils.CurrentUser.Id)
            {
                return RedirectToAction("Error", "Error");
            }

            model.TagLine = String.Join(", ", sub.SubroutineTags.Select(st => st.Tag.Name).ToArray());
            model.InputRegisters = new List<InputRegisterDataWrapper>();
            model.OutputRegisters = new List<InputRegisterDataWrapper>();
            model.Stacks = new List<StackWrapper>();
            model.Code = sub.Code;
            model.Description = sub.Description;
            model.Name = sub.Title;

            for (int i = 0; i < 5; i ++ )
            {
                var current = sub.InputStackParams.FirstOrDefault(s => s.Offset == i);

                if (current == null)
                {
                    model.Stacks.Add(new StackWrapper {Offset = i, Comment = String.Empty});
                }
                else
                {
                    model.Stacks.Add(new StackWrapper {Offset = current.Offset, Comment = current.Description});
                }
            }

            foreach (var register in allRegisters)
            {
                var usedInInput = sub.SubroutineInputRegisters.FirstOrDefault(sir => sir.RegisterId == register.Id);
                var usedInOutput = sub.SubroutineOutputRegisters.FirstOrDefault(sor => sor.RegisterId == register.Id);

                model.InputRegisters.Add(new InputRegisterDataWrapper { Comment = usedInInput == null ? String.Empty : usedInInput.Description, IsUsed = usedInInput != null, Name = register.Name, RegisterId = register.Id });
                model.OutputRegisters.Add(new InputRegisterDataWrapper { Comment = usedInOutput == null ? String.Empty : usedInOutput.Description, IsUsed = usedInOutput != null, Name = register.Name, RegisterId = register.Id });
            }

            return View("Edit", model);
        }

        [HttpPost]
        public ActionResult Submit()
        {
            var validationParse = false;

            Int64 id = 0;
            String name = String.Empty;
            String description = String.Empty;
            String code = String.Empty;

            if (Request["id"] != null)
            {
                try
                {
                    id = Int64.Parse(Request["id"]);
                }
                catch (Exception)
                {
                    return RedirectToAction("Error", "Error");
                }
            }

            if (Request["SubroutineName"] != null)
            {
                try
                {
                    name = Request["SubroutineName"];
                }
                catch (Exception)
                {
                    return RedirectToAction("Error", "Error");
                }
            }

            String tagList = String.Empty;
            if (Request["TagList"] != null)
            {
                try
                {
                    tagList = Request["TagList"];
                }
                catch(Exception)
                {
                    return RedirectToAction("Error", "Error");
                }
            }

            var separatedTags = tagList.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim());
            
            if (Request["Description"] != null)
            {
                try
                {
                    description = Request["Description"];
                }
                catch (Exception)
                {
                    return RedirectToAction("Error", "Error");
                }
            }

            if (Request["Code"] != null)
            {
                try
                {
                    code = Request["Code"];
                }
                catch (Exception)
                {
                    return RedirectToAction("Error", "Error");
                }
            }

            if (LoginUtils.CurrentUser == null || !LoginUtils.CurrentUser.IsLogged)
            {
                return RedirectToAction("Error", "Error");
            }



            var subRepo = (ISubroutineRepository) DependencyResolver.Current.GetService(typeof (ISubroutineRepository));

            if (id == 0)
            {
                var sub = new Subroutine();
                sub.Code = code;
                sub.Description = description;
                sub.Title = name;
                sub.UserId = LoginUtils.CurrentUser.Id;
                sub.Date = DateTime.Now;
                subRepo.Create(sub);
                id = sub.Id;
            }
            else
            {
                var sub = subRepo.Read(id);

                if (sub.UserId != LoginUtils.CurrentUser.Id)
                {
                    return RedirectToAction("Error", "Error");
                }

                sub.Code = code;
                sub.Description = description;
                sub.Title = name;
                sub.Date = DateTime.Now;
                subRepo.Update(sub);
            }

            var tagRepo = DependencyResolver.Current.GetService<ITagRepository>();
            var subTagRepo = DependencyResolver.Current.GetService<ISubroutineTagRepository>();
            var existingSubTags = subTagRepo.Query(st => st.SubroutineId == id).ToList();
            foreach (var existingSubTag in existingSubTags)
            {
                subTagRepo.Delete(existingSubTag);
            }

            foreach (var separatedTag in separatedTags)
            {
                var existingTag = tagRepo.Query(t => t.Name == separatedTag).FirstOrDefault();
                if (existingTag == null)
                {
                    var newTag = new Tag
                                     {
                                         Name = separatedTag
                                     };
                    tagRepo.Create(newTag);
                    subTagRepo.Create(new SubroutineTag { SubroutineId = id, TagId = newTag.Id});
                }
                else
                {
                    subTagRepo.Create(new SubroutineTag {SubroutineId = id, TagId =  existingTag.Id});
                }
            }

            var regRepo = DependencyResolver.Current.GetService<IRegisterRepository>();
            var registers = regRepo.Query();
            var inputRegs = new Dictionary<long, String>();
            var outputRegs = new Dictionary<long, String>();
            foreach (var reg in registers)
            {
                if (Request[String.Format("input_used_{0}", reg.Id)] != null && Request[String.Format("input_used_{0}", reg.Id)] == "on")
                {
                    inputRegs.Add(reg.Id, Request[String.Format("input_text_{0}", reg.Id)] ?? String.Empty);
                }
                if (Request[String.Format("output_used_{0}", reg.Id)] != null && Request[String.Format("output_used_{0}", reg.Id)] == "on")
                {
                    outputRegs.Add(reg.Id, Request[String.Format("output_text_{0}", reg.Id)] ?? String.Empty);
                }
            }

            var inputRegRepo = DependencyResolver.Current.GetService<ISubroutineInputRegisterRepository>();
            var existingRegs = inputRegRepo.Query(ir => ir.SubroutineId == id).ToList();
            foreach (var subroutineInputRegister in existingRegs)
            {
                inputRegRepo.Delete(subroutineInputRegister);
            }

            foreach (var inputReg in inputRegs)
            {
                var newInputReg = new SubroutineInputRegister
                                      {
                                          Description = inputReg.Value,
                                          RegisterId = inputReg.Key,
                                          SubroutineId = id,
                                      };
                inputRegRepo.Create(newInputReg);
            }

            var outputRegRepo = DependencyResolver.Current.GetService<ISubroutineOutputRegisterRepository>();
            var existingOutputRegs = outputRegRepo.Query(ir => ir.SubroutineId == id).ToList();
            foreach (var subroutineOutputRegister in existingOutputRegs)
            {
                outputRegRepo.Delete(subroutineOutputRegister);
            }

            foreach (var outputReg in outputRegs)
            {
                var newOutputReg = new SubroutineOutputRegister
                {
                    Description = outputReg.Value,
                    RegisterId = outputReg.Key,
                    SubroutineId = id,
                };
                outputRegRepo.Create(newOutputReg);
            }

            var stackRepo = DependencyResolver.Current.GetService<IInputStackParamRepository>();
            var existingParams = stackRepo.Query(s => s.SubroutineId == id).ToList();
            foreach (var inputStackParam in existingParams)
            {
                stackRepo.Delete(inputStackParam);
            }

            for (int i = 0; i < 5; i++)
            {
                if (Request[String.Format("input_stack_{0}", i)] != null && !String.IsNullOrEmpty(Request[String.Format("input_stack_{0}", i)]))
                {
                    var newStack = new InputStackParam
                                       {
                                           Description = Request[String.Format("input_stack_{0}", i)],
                                           Offset = i,
                                           SubroutineId = id
                                       };
                    stackRepo.Create(newStack);
                }
            }

            
            var request = Request;
            return RedirectToAction("Detail", new {id = id});
        }

        public ActionResult UpVote(long id)
        {
            if (LoginUtils.CurrentUser == null || !LoginUtils.CurrentUser.IsLogged)
            {
                return RedirectToAction("Error", "Error");
            }

            var subRepo = DependencyResolver.Current.GetService<ISubroutineRepository>();
            var targetSub = subRepo.Read(id);
            if (targetSub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            if (targetSub.UserVotes.Any(uv => uv.UserId == LoginUtils.CurrentUser.Id))
            {
                return RedirectToAction("Error", "Error");
            }

            var voteRepo = DependencyResolver.Current.GetService<IUserVoteRepository>();
            var newUpVote = new UserVote
                                {
                                    Amount = 1,
                                    SubroutineId = id,
                                    UserId = LoginUtils.CurrentUser.Id,
                                };
            voteRepo.Create(newUpVote);

            return RedirectToAction("Detail", new { id = id });
        }

        public ActionResult DownVote(long id)
        {
            if (LoginUtils.CurrentUser == null || !LoginUtils.CurrentUser.IsLogged)
            {
                return RedirectToAction("Error", "Error");
            }

            var subRepo = DependencyResolver.Current.GetService<ISubroutineRepository>();
            var targetSub = subRepo.Read(id);
            if (targetSub == null)
            {
                return RedirectToAction("Error", "Error");
            }

            if (targetSub.UserVotes.Any(uv => uv.UserId == LoginUtils.CurrentUser.Id))
            {
                return RedirectToAction("Error", "Error");
            }

            var voteRepo = DependencyResolver.Current.GetService<IUserVoteRepository>();
            var newUpVote = new UserVote
            {
                Amount = -1,
                SubroutineId = id,
                UserId = LoginUtils.CurrentUser.Id,
            };
            voteRepo.Create(newUpVote);

            return RedirectToAction("Detail", new { id = id });
        }
    }
}

